
import sys
import os
import re
import json
import xmindparser
import time
import random
from itertools import islice
import functools

# 自定义重试装饰器
def retry(stop_max_attempt_number=3, wait_fixed=2000):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < stop_max_attempt_number:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    attempts += 1
                    if attempts >= stop_max_attempt_number:
                        raise
                    time.sleep(wait_fixed / 1000.0)  # 转换为秒
            return func(*args, **kwargs)  # 最后一次尝试，不捕获异常
        return wrapper
    return decorator

# 不再添加项目路径到环境

# 导入必要的模块
from comm.common_code import get_excel_data, logger, log_to_ui_info, log_to_ui_error

from tapd_open_api.case.dir_get import dir_get_id_and_name
from tapd_open_api.case.dir_create import dir_create
from tapd_open_api.case.case_associated_requirements import create_test_cases_association_requirements
from tapd_open_api.case.case_get import tcases_get
from tapd_capture_api.capture_get_dir_jsp import capture_get_dir_jsp
from tapd_open_api.case.case_batch_import import tcases_batch_save

# 添加导入过程提示弹窗（非阻塞，不影响流程，一直显示直到用户关闭）
from PyQt5.QtWidgets import QMessageBox

excel_data = get_excel_data()

# 在文件开头添加全局配置
MIN_REQUEST_DELAY = 1.0  # 最小请求间隔(秒) - 增加延迟
MAX_REQUEST_DELAY = 2.0  # 最大请求间隔(秒) - 增加延迟

# 固定提示：
gd_messages= "提示：导入过程可能会出现【未响应】，不要关闭软件，这是正常的！后台一直在导入用例，导入完成后会恢复正常！"

# 移除重试装饰器以避免重复弹窗
def __check_xmind_file_and_case_list(xmind_path, level=0):
    """处理XMind文件，提取需求ID和一级节点标题，并检查是否继续,如果继续,则返测试用例列表"""
    
    # 初始化需求ID
    xmind_requirement_id = None

    log_to_ui_info(gd_messages)# 添加提示

    try:
        # 处理XMind文件
        xmind_content = xmindparser.xmind_to_dict(xmind_path)
    except Exception as e:
        logger.error(f"处理XMind文件时发生错误: {e}")
        log_to_ui_error(f"处理XMind文件时发生错误: {e}")
        return None, None

    # 提取需求ID - 改进的逻辑
    json_str = xmind_content[0]['topic']['title']
    
    # 尝试多种格式提取需求ID
    patterns = [
        r'需求ID[：:]\s*(\d+)',
        r'需求id[：:]\s*(\d+)',
        r'requirement_id[：:]\s*(\d+)'
    ]
    
    for pattern in patterns:
        match = re.search(pattern, json_str, re.IGNORECASE)
        if match:
            xmind_requirement_id = match.group(1)
            logger.info(f'提取到需求ID: {xmind_requirement_id}')
            log_to_ui_info(f'提取到需求ID: {xmind_requirement_id}')
            break
    
    # 如果没有找到，尝试解析JSON
    if not xmind_requirement_id and ('{' in json_str and '}' in json_str):
        try:
            data = json.loads(json_str)
            xmind_requirement_id = data.get('requirement_id') or data.get('需求ID')
            if xmind_requirement_id:
                logger.info(f'从JSON提取到需求ID: {xmind_requirement_id}')
                log_to_ui_info(f'从JSON提取到需求ID: {xmind_requirement_id}')
        except json.JSONDecodeError:
            logger.error('无法解析JSON格式的需求ID')
            log_to_ui_error('无法解析JSON格式的需求ID')

    # 获取一级目录检查配置
    first_level_check = excel_data.get('一级目录检查', '')
    if first_level_check:
        first_level_check = [item.strip() for item in first_level_check.split('、')]
    else:
        first_level_check = []
    logger.info(f'一级目录检查配置: {first_level_check}')
    log_to_ui_info(f'一级目录检查配置: {first_level_check}')

    if not xmind_content or not xmind_content[0].get('topic'):
        logger.error('XMind文件格式错误，无法提取需求ID')
        log_to_ui_error('XMind文件格式错误，无法提取需求ID')
        return None, None

    # 提取所有一级节点标题
    try:
        titles = []
        if xmind_content[0]['topic'].get('topics'):
            titles = [module['title'] for module in xmind_content[0]['topic']['topics']]
        logger.info(f'XMind文件中所有第一个节点标题: {titles}')
        log_to_ui_info(f'XMind文件中所有第一个节点标题: {titles}')
        
    except Exception as e:
        titles = []
        logger.error(f'获取节点标题时发生错误: {str(e)}')
        log_to_ui_error(f'获取节点标题时发生错误: {str(e)}')



    # 如果需求ID不为空且格式正确，且所有一级节点标题都在检查配置中，则弹窗确认
    if (xmind_requirement_id and len(xmind_requirement_id) == 19 and 
        first_level_check and all(title in first_level_check for title in titles)):
        reply = QMessageBox.question(None, "格式正确，确认是否导入", 
                                    "您填写的需求ID和一级目录已符合要求!\n\n" + gd_messages + "\n\n是否确认导入？",
                                    QMessageBox.Yes | QMessageBox.No, 
                                    QMessageBox.Yes)
        
        if reply == QMessageBox.No:
            logger.info('用户取消导入')
            log_to_ui_info('用户取消导入')
            return None, None
        logger.info('用户确认导入')
        log_to_ui_info('用户确认导入')
        log_to_ui_info(f"导入过程可能会出现【未响应】，不要关闭软件，后台一直在导入用例，这是正常的！导入完成后会恢复正常！")

    # 如果需求ID为空，或XMind文件中任何一个第一个节点标题不在一级目录检查配置中，则需要确认是否继续操作
    elif not xmind_requirement_id or len(xmind_requirement_id) != 19 or (first_level_check and any(title not in first_level_check for title in titles)):
        # 构建提示消息
        error_messages = []

        # 检查需求ID问题
        if not xmind_requirement_id or len(xmind_requirement_id) != 19:
            req_id_display = str(xmind_requirement_id) if xmind_requirement_id else "空"
            error_messages.append(f"检测到需求ID为：{req_id_display}，存在错误(需求ID格式应为19位数字，否则会关联需求失败)")
        
        # 检查目录问题
        if first_level_check and any(title not in first_level_check for title in titles):
            error_messages.append(f"第一个节点目录:{titles}中存在不在{first_level_check}中的目录，请按产品功能路径修改脑图目录！")
        
        # 根据问题类型提供不同的提示
        if len(error_messages) == 2:
            # 需求和目录都有问题
            message = "1、" + error_messages[0] + "\n\n2、" + error_messages[1] + "\n\n" + gd_messages + "\n\n是否继续导入？"
        elif not xmind_requirement_id or len(xmind_requirement_id) != 19:
            # 只有需求ID有问题
            message = error_messages[0] + "\n\n" + gd_messages + "\n\n是否继续导入？"
        else:
            # 只有目录有问题
            message = error_messages[0] + "\n\n" + gd_messages + "\n\n是否继续导入？"
        
        reply = QMessageBox.question(None, "格式错误，确认是否导入", 
                                    message,
                                    QMessageBox.Yes | QMessageBox.No, 
                                    QMessageBox.No)
        
        if reply == QMessageBox.No:
            logger.info('用户取消操作')
            log_to_ui_info('用户取消操作')
            return None, None
        logger.info('用户确认继续操作')
        log_to_ui_info('用户确认继续操作')

                    

    results = []  # 存储提取结果
    def traverse(node, current_path, module_name):
        """递归遍历xmind节点"""
        if 'topics' in node:
            for topic in node['topics']:
                new_path = current_path + [topic['title']]
                if 'topics' in topic:
                    traverse(topic, new_path, module_name)
                else:
                    # 提取叶子节点作为测试用例[需求ID,用例目录,用例名称,用例步骤,预期结果]
                    if level >= 3 and len(new_path) > level + 1:
                        case_directory = module_name + "-" + "-".join(new_path[:level-1])
                    elif len(new_path) == 2 or len(new_path) == 1:
                        case_directory = module_name
                    else:
                        case_directory = module_name + "-" + "-".join(new_path[:-2])
                    case_name = new_path[-2] + "-" + new_path[-1]
                    case_steps = module_name + " >> " + " >> ".join(new_path[:-1])
                    one_case = [xmind_requirement_id, case_directory, case_name, case_steps, new_path[-1]]
                    logger.info(f'提取到用例:\n {one_case}')
                    results.append(one_case)
    
    # 处理每个模块
    for module in xmind_content[0]['topic']['topics']:
        module_name = module['title']
        if 'topics' in module:
            for func in module['topics']:
                traverse(func, [func['title']], module_name)

    logger.info(f'提取到用例列表: {results}')
    log_to_ui_info(f'提取到用例列表: {results}')
    logger.info(f'提取到用例总数: {len(results)}')
    log_to_ui_info(f'提取到用例总数: {len(results)}')
    return xmind_requirement_id, results


def xmind_to_tapd_add(xmind_dir_path, level=0):
    """将XMind文件导入到TAPD系统"""

    # 获取需求ID和用例列表
    xmind_requirement_id, xmind_cases_list = __check_xmind_file_and_case_list(xmind_dir_path, level=level)
    
    # 首先检查是否返回了None，如果是则直接返回
    if xmind_cases_list is None:
        return None, None
    
    # if len(xmind_cases_list) <= 50:
    #     # xmind_cases_list倒序
    #     xmind_cases_list.reverse()
    # 处理每个用例
    for case in xmind_cases_list:
        # 处理用例目录
        case_directory = case[1]
        # 处理用例步骤
        case_steps = case[3]
        # 处理用例预期结果
        case_expected_result = case[4]
        # 处理用例名称
        case_name = case[2]
        # 处理用例描述
        case_description = case[2]
        # 处理用例前置条件
        case_preconditions = case[2]



    # 初始化失败用例列表
    failed_cases = []

    # 目录去重
    deduplication_dir_list = []
    for case in xmind_cases_list:
        if case[1] not in deduplication_dir_list:
            deduplication_dir_list.append(case[1])
    print(deduplication_dir_list)

    # 获取TAPD目录
    tapd_exists_dir_dict = capture_get_dir_jsp()

    # 已存在，无需创建的目录列表
    exists_dir_list = []
    # 已创建成功的目录列表
    created_dir_list = []

    for dir_total in deduplication_dir_list:
        if dir_total not in tapd_exists_dir_dict:
            parts = dir_total.split('-')
            current_path = ""
            for i, part in enumerate(parts):
                parent_path = current_path
                
                if i == 0:
                    current_path = part
                else:
                    current_path = current_path + '-' + part 
                
                if i == 0:  # 根目录
                    if current_path not in tapd_exists_dir_dict:
                        dir_id = dir_create(current_path)
                        tapd_exists_dir_dict[current_path] = dir_id
                        created_dir_list.append(current_path)
                        log_to_ui_info(f'创建根目录{current_path}成功，目录id为{dir_id}')
                        logger.info(f'创建根目录{current_path}成功，目录id为{dir_id}')
                    else:
                        log_to_ui_info(f"根目录: '{current_path}'已存在")
                        logger.info(f"根目录: '{current_path}'已存在")
                else:
                    if current_path not in tapd_exists_dir_dict:
                        parent_id = tapd_exists_dir_dict[parent_path]
                        dir_id = dir_create(part, parent_id)
                        tapd_exists_dir_dict[current_path] = dir_id
                        created_dir_list.append(current_path)
                        log_to_ui_info(f'父目录：{parent_path}，创建子目录：{current_path}成功，目录id为{dir_id}')
                        logger.info(f'父目录：{parent_path}，创建子目录：{current_path}成功，目录id为{dir_id}')
                    else:
                        log_to_ui_info(f"子目录: '{current_path}'已存在")
                        logger.info(f"子目录: '{current_path}'已存在")
        else:
            exists_dir_list.append(dir_total)
            log_to_ui_info(f"目录: '{dir_total}'已存在")
            logger.info(f"目录: '{dir_total}'已存在")
    
    logger.info(f'已存在的目录列表: \n{exists_dir_list}')
    log_to_ui_info(f'已存在的目录列表: \n{exists_dir_list}')
    logger.info(f'已创建成功的目录列表: \n{created_dir_list}')
    log_to_ui_info(f'已创建成功的目录列表: \n{created_dir_list}')

    # 构造批量导入的测试用例数据
    all_cases_to_import = []
    
    for xmind_data_case in xmind_cases_list:
        category_id = tapd_exists_dir_dict.get(xmind_data_case[1])
        
        case_data = {
            "access_token": excel_data['access_token'],
            "workspace_id": excel_data['项目ID'],
            "category_id": category_id,
            "name": xmind_data_case[2],
            "precondition": "",
            "steps": xmind_data_case[3],
            "expectation": xmind_data_case[4],
            "creator": excel_data['创建人'],
        }
        
        all_cases_to_import.append(case_data)
            
    logger.info(f'共构造 {len(all_cases_to_import)} 个测试用例数据')
    log_to_ui_info(f'共构造 {len(all_cases_to_import)} 个测试用例数据')

    #all_cases_to_import 倒序，确保先创建的目录先处理
    all_cases_to_import.reverse()

    # 使用迭代器方式处理大列表，每200条数据执行一次批量导入
    total_items = len(all_cases_to_import)
    batch_size = 200
    num_batches = (total_items + batch_size - 1) // batch_size

    logger.info(f"总共 {total_items} 条数据，将分成 {num_batches} 批处理，每批最多 {batch_size} 条")
    log_to_ui_info(f"总共 {total_items} 条数据，将分成 {num_batches} 批处理，每批最多 {batch_size} 条")

    # 统计变量
    total_success = 0
    total_failed = 0
    association_success = 0  # 关联需求成功计数
    association_failed = 0   # 关联需求失败计数
    association_failed_cases = []  # 关联需求失败的用例ID列表
    
    # 分批处理（从最后一批到第一批）
    for i in range(num_batches-1, -1, -1):  # 从最大索引开始递减到0
        start_idx = i * batch_size
        end_idx = min((i + 1) * batch_size, total_items)
        
        batch_data = all_cases_to_import[start_idx:end_idx]
        
        logger.info(f"\n处理第 {i+1} 批数据 (索引 {start_idx}-{end_idx-1}):")
        log_to_ui_info(f"\n处理第 {i+1} 批数据 (索引 {start_idx}-{end_idx-1}):")
        logger.info(f'本批次处理 {len(batch_data)} 条用例')
        log_to_ui_info(f'本批次处理 {len(batch_data)} 条用例')
        batch_data.reverse() # 逆序处理
        all_success_cases, failed_cases = tcases_batch_save(batch_data)
        
        # 更新导入成功和失败计数
        total_success += len(all_success_cases['data'])
        total_failed += len(failed_cases)
        
        # 关联需求
        batch_association_success = 0  # 本批次关联成功计数
        batch_association_failed = 0   # 本批次关联失败计数
        # 只有在有需求ID的情况下才执行关联操作
        if xmind_requirement_id and len(xmind_requirement_id) == 19:
            for success_cases in all_success_cases['data']:
                case_id = success_cases['Tcase']['id']
                response_code, response = create_test_cases_association_requirements(xmind_requirement_id, case_id)
                
                # 统计关联结果
                if response_code == 200:
                    batch_association_success += 1
                    association_success += 1
                else:
                    batch_association_failed += 1
                    association_failed += 1
                    association_failed_cases.append(case_id)
        else:
            # 如果没有需求ID，记录日志
            logger.info(f'跳过关联需求：需求ID为空或格式不正确 ({xmind_requirement_id})')
            log_to_ui_info(f'跳过关联需求：需求ID为空或格式不正确 ({xmind_requirement_id})')
    # 记录总关联结果
    if xmind_requirement_id and len(xmind_requirement_id) == 19:
        logger.info(f'\n导入完成统计: 成功导入 {total_success} 条，失败 {total_failed} 条；关联需求成功 {association_success} 条，失败 {association_failed} 条')
        log_to_ui_info(f'\n导入完成统计: 成功导入 {total_success} 条，失败 {total_failed} 条；关联需求成功 {association_success} 条，失败 {association_failed} 条')
    else:
        logger.info(f'\n导入完成统计: 成功导入 {total_success} 条，失败 {total_failed} 条；未关联需求（需求ID: {xmind_requirement_id}）')
        log_to_ui_info(f'\n导入完成统计: 成功导入 {total_success} 条，失败 {total_failed} 条；未关联需求（需求ID: {xmind_requirement_id}）')
    
    return total_success, total_failed

if __name__ == '__main__':
    # 获取示例文件路径
    from ui_code.common_ui import get_project_root
    xmind_dir_path = get_project_root() / "files" / "xmind9" / "目录测试" / "【示例】商品详情qwer12345.xmind"
    # 调用合并后的方法处理XMind文件
    xmind_to_tapd_add(str(xmind_dir_path))